package com.zhangfb95.crosschain.plugin.container.java.fabric2.controller;

import com.google.protobuf.ByteString;
import com.zhangfb95.crosschain.infra.exception.BusinessException;
import com.zhangfb95.crosschain.infra.model.Result;
import com.zhangfb95.crosschain.infra.util.Base64Util;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.infra.config.properties.FabricProperties;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.infra.model.controller.dto.ChainPathInfo;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.infra.model.controller.request.chain.GetHeightReq;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.infra.model.controller.request.chain.QueryBlockByNumberReq;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.infra.model.controller.request.chain.QueryByChaincodeReq;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.infra.model.controller.request.chain.QueryInstalledChainCodesReq;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.infra.model.controller.request.chain.QueryTransactionByIDReq;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.infra.model.controller.request.chain.SendReq;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.infra.model.controller.response.chain.GetChainNameRes;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.infra.model.controller.response.chain.GetHeightRes;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.infra.model.controller.response.chain.QueryBlockByNumberRes;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.infra.model.controller.response.chain.QueryByChaincodeRes;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.infra.model.controller.response.chain.QueryInstalledChainCodesRes;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.infra.model.controller.response.chain.QueryTransactionByIDRes;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.infra.model.controller.response.chain.SendRes;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.service.FabricService;
import com.zhangfb95.crosschain.plugin.container.java.fabric2.service.impl.ChainPathServiceImpl;
import org.hyperledger.fabric.protos.common.Common;
import org.hyperledger.fabric.sdk.BlockEvent;
import org.hyperledger.fabric.sdk.BlockInfo;
import org.hyperledger.fabric.sdk.TransactionInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author zhangfubing
 * @since 2023/10/23
 */
@RequestMapping("/chain")
@RestController
public class ChainController {

    @Autowired
    private FabricProperties fabricProperties;
    @Autowired
    private FabricService fabricService;
    @Autowired
    private ChainPathServiceImpl chainPathService;

    @GetMapping("/getChainName")
    public Result<GetChainNameRes> getChainName() {
        return Result.ok(new GetChainNameRes().setChainName(fabricProperties.getChainName()));
    }

    /**
     * 发送交易
     */
    @PostMapping("/send")
    public Result<SendRes> send(@RequestBody @Validated SendReq req) {
        ChainPathInfo chainPathInfo = chainPathService.build(req.getChainPath());
        String channelName = chainPathInfo.getChannelName();
        String chaincodeName = chainPathInfo.getChaincodeName();
        BlockEvent.TransactionEvent transactionEvent =
                fabricService.sendTransaction(
                        req.getServerId(),
                        req.getUserId(),
                        req.getChainPath(),
                        channelName,
                        chaincodeName,
                        req.getMethod(),
                        req.getArgs());
        SendRes res = new SendRes()
                .setTransactionID(transactionEvent.getTransactionID())
                .setResult(new String(transactionEvent.getTransactionActionInfo(0).getProposalResponsePayload()));
        return Result.ok(res);
    }

    /**
     * 查询链码
     */
    @PostMapping("/queryByChaincode")
    public Result<QueryByChaincodeRes> queryByChaincode(@RequestBody @Validated QueryByChaincodeReq req) {
        ChainPathInfo chainPathInfo = chainPathService.build(req.getChainPath());
        String channelName = chainPathInfo.getChannelName();
        String chaincodeName = chainPathInfo.getChaincodeName();
        String result = fabricService.queryByChaincode(
                req.getServerId(),
                req.getUserId(),
                req.getChainPath(),
                channelName,
                chaincodeName,
                req.getMethod(),
                req.getArgs());
        return Result.ok(new QueryByChaincodeRes().setResult(result));
    }

    /**
     * 获取区块高度
     */
    @PostMapping("/getHeight")
    public Result<GetHeightRes> getHeight(@RequestBody @Validated GetHeightReq req) {
        ChainPathInfo chainPathInfo = chainPathService.build(req.getChainPath());
        String channelName = chainPathInfo.getChannelName();
        long height = fabricService.getHeight(channelName);
        return Result.ok(new GetHeightRes().setHeight(height));
    }

    /**
     * 根据区块编号获取区块
     */
    @PostMapping("/queryBlockByNumber")
    public Result<QueryBlockByNumberRes> queryBlockByNumber(@RequestBody @Validated QueryBlockByNumberReq req) {
        ChainPathInfo chainPathInfo = chainPathService.build(req.getChainPath());
        String channelName = chainPathInfo.getChannelName();
        BlockInfo blockInfo = fabricService.queryBlockByNumber(channelName, req.getBlockNumber());
        List<String> txIds = getTxIds(blockInfo.getBlock().toByteArray());
        QueryBlockByNumberRes res = new QueryBlockByNumberRes()
                .setBlockNumber(blockInfo.getBlockNumber())
                .setDataHash(Base64Util.encodeToString(blockInfo.getDataHash()))
                .setBlockHash(Base64Util.encodeToString(fabricService.calculateBlockHash(blockInfo)))
                .setPreviousHash(Base64Util.encodeToString(blockInfo.getPreviousHash()))
                .setBlockBytes(blockInfo.getBlock().toByteArray())
                .setTxIds(txIds);
        return Result.ok(res);
    }

    /**
     * 根据交易id获取交易信息
     */
    @PostMapping("/queryTransactionByID")
    public Result<QueryTransactionByIDRes> queryTransactionByID(@RequestBody @Validated QueryTransactionByIDReq req) {
        ChainPathInfo chainPathInfo = chainPathService.build(req.getChainPath());
        String channelName = chainPathInfo.getChannelName();
        TransactionInfo transaction = fabricService.queryTransactionByID(channelName, req.getTxId());
        QueryTransactionByIDRes res = new QueryTransactionByIDRes()
                .setTxId(transaction.getTransactionID())
                .setTransactionBytes(transaction.getEnvelope().toByteArray());
        return Result.ok(res);
    }

    /**
     * 查询安装的通道链码列表
     */
    @PostMapping("/queryInstalledChainCodes")
    public Result<QueryInstalledChainCodesRes> queryInstalledChainCodes(
            @RequestBody @Validated QueryInstalledChainCodesReq req) {
        Map<String, Map<String, String>> chainCodeName2VersionMap =
                fabricService.queryInstalledChainCodes(req.getChannelName());
        return Result.ok(new QueryInstalledChainCodesRes().setChannel2chainCode2VersionMap(chainCodeName2VersionMap));
    }

    private List<String> getTxIds(byte[] blockBytes) {
        List<String> txIds = new ArrayList<>();
        try {
            Common.Block block = Common.Block.parseFrom(blockBytes);
            for (ByteString envelopeBytes : block.getData().getDataList()) {
                Common.Envelope envelope = Common.Envelope.parseFrom(envelopeBytes);
                Common.Payload payload = Common.Payload.parseFrom(envelope.getPayload());
                Common.ChannelHeader channelHeader =
                        Common.ChannelHeader.parseFrom(payload.getHeader().getChannelHeader());
                String txID = channelHeader.getTxId();
                txIds.add(txID);
            }
        } catch (Exception e) {
            throw new BusinessException(e);
        }
        return txIds;
    }
}
